小明來問說, 他想要檢查輸入內容是否驗證通過,
檢查模型(Model)的所有屬性(Property)內容,
查看裡面某些屬性(Property) 內容或是格式是不是有問題?
以下是小明提供的程式碼片段, 它想要驗證customer 內容是不是有問題
public void Process(Customer customer)
{
if( customer.UserId <= 0 ) {
throw new Exception("UserId must be a positive number");
}
if( customer.Email == null ){
throw new Exception("Email can't be null");
}
...
}
驗證輸入內容的方法有很多種, 這沒有唯一正確的標準答案,
因此與團隊合作了解哪種方法最適合解決目前遭遇的問題,
就是好的方法.
通常驗證輸入內容有三種方法
此方法使用 Exception 涉及直接系統中斷, 該模式是最常用的驗證模式, 它包括直接檢查輸入和引發異常.
就像小明來詢問, 所提供的程式碼片段寫法.
此方法的特色如下
驗證規則模式源自Visitor 設計模式, 我們可以使用dotnet 提供的Validator,
dotnet 提供了許多常用的ValidationAttribute , 常見的有
Attributes | 說明 |
---|---|
Required | 必填 |
StringLength(20) | 字串長度 |
MinLength(2) | 最小字串長度 |
MaxLength(2) | 最大字串長度 |
Range(18, 20) | 數值範圍 |
EmailAddress | Email格式 |
dotnet Validator 範例如下
public class Customer
{
public int UserId { get;set; }
[Required]
public string Name { get; set;}
[EmailAddress]
public string Email { get; set; }
}
public void Process(Customer customer)
{
var context = new ValidationContext(customer, null, null);
Validator.ValidateObject(customer, context, true);
...
}
透過dotnet 提供的常用Atturibute 就能應付一般大部分的驗證情況,
不過有時候我們還是需要自訂的驗證方式.
第一種是自訂自己的Validation Attribute, 下面示範用來檢查欄位的內容是否為 null
public class MyRequiredValidationAttribute : ValidationAttribute
{
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
var text = (string) value;
if ( String.IsNullOrEmpty(text) )
{
return new ValidationResult("Name can't be null!",
new[] { validationContext.MemberName });
}
return base.IsValid(value, validationContext);
}
}
然後在Customer 物件中, 掛載MyRequired Attribute
public class Customer
{
[MyRequired]
public string Name { get; set;}
}
也有與 Required 同樣的效果.
也許有的人會不喜歡在Customer 中掛載許多驗證 Attribute 來設定驗證方法,
想要統一在一個地方做設定驗證方法.
那你可以用第二種方式, 實作IValidatableObject , 範例如下
public class Customer : IValidatableObject
{
public int UserId { get;set; }
public string Name { get; set;}
public string Email { get; set; }
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
if ( String.IsNullOrEmpty(Name) )
{
yield return new ValidationResult("Name can't be null", new[] {" Name "});
}
}
}
此模式具有以下優點
缺點
用dotnet Validator 的使用方式如下
public void Process(Customer customer) {
Validator.ValidateObject(customer, context, true);
var validationResults = new Collection<ValidationResult>();
var isSuccess = Validator.TryValidateObject(customer, context, validationResults, true);
...
}
上述程式碼 validationResults 變數儲存所有驗證的結果.
dotnet Validator 提供兩種方式 ValidateObject 和 TryValidateObject
我們也能利用 FluentValidation 來達到這個方式, 它的使用方式如下
public class MyValidator : AbstractValidator<Customer>
{
public MyValidator()
{
ValidatorOptions.CascadeMode = CascadeMode.StopOnFirstFailure;
RuleFor(c => c.Name)
.NotNull()
.WithMessage("{PropertyName} can't be null");
...
}
}
public void Process(Customer customer)
{
var validator = new MyValidator();
var validationResult = validator.Validate(customer);
if (!validationResult.IsValid)
{
var errorMssage = string.Join("\r\n", validationResult.Errors.Select(e => e.ErrorMessage));
throw new Exception(errorMessage);
}
}
你可以在一個地方添加許多RuleFor 驗證規則.
當然FluentValidation 也提供我們自訂驗證規則, 自訂驗證規則方式如下
public class MyRequiredValidator : PropertyValidator {
public MyRequiredValidator()
: base("'{PropertyValue}' can't not be null.") { }
protected override bool IsValid(PropertyValidatorContext context) {
var text = (string) context.PropertyValue;
if (string.IsNullOrEmpty(text)) {
return false;
}
return true;
}
}
寫好自訂的 PropertyValidator 之後, 透過 SetValidator 照下面示範
public class MyValidator : AbstractValidator<Customer>
{
public MyValidator()
{
ValidatorOptions.CascadeMode = CascadeMode.StopOnFirstFailure;
RuleFor(c => c.Name)
.SetValidator(new MyRequiredValidator());
...
}
}
到這裡你可以發現dotnet 和FluentValidation 套用 MyRequired 驗證規則的方式不同.
dotnet Validation 通常方式是在Customer 物件中掛載驗證規則
[MyRequired]
public string Name{ get; set;}
FluentValidation 通常方式是在自訂的MyValidator 驗證器中掛載驗證規則
public class MyValidator : AbstractValidator<Customer> {
public MyValidator()
{
RuleFor(c => c.Name)
.SetValidator(new MyRequiredValidator());
...
}
}
而FluentValidation 只有這一種執行驗證方式
public void Process(Customer customer) {
var validator = new MyValidator();
var validationResult = validator.Validate(customer);
if (!validationResult.IsValid)
{
var errorMssage = string.Join("\r\n", validationResult.Errors.Select(e => e.ErrorMessage));
...
}
}
但你會發現, 假如應用程式有很多個模型(models),
用 FluentValidation 就必須為每個模型(model)編寫 Validators.
也許有人會覺得很麻煩. 會想要寫一個通用驗證器(Validator).
而dotnet Validator 恰恰就正好是一個通用的驗證器, 兩者作法不同.
另外在FluentValidation 中, 或許你會覺得每增加自訂驗證規則還要多寫 PropertyValidator 很麻煩,
你也可以用 Custom 方法來提供自訂驗證規則.
public class MyValidator : AbstractValidator<Customer> {
public MyValidator()
{
RuleFor(c => c.Name)
.Custom((value, context) =>
{
if (string.IsNullOrEmpty(value))
{
context.AddFailure("Name", "Name can't be null");
}
});
}
}
一旦用Custom 撰寫自訂驗證規則, 你就很難在其他地方重複使用.
無論採用PropertyValidator 或是Custom 方法編寫自訂驗證規則,
我認為這取決於你要放入其中的邏輯類型.
如果您想以更容易重複使用的方式編寫它, 請堅持使用PropertyValidator.
如果要使用Custom 的樣式編寫, 請使用類似 AbstractValidator 派生類.
在很多種情況下, 確實沒有對/錯的方法.
這種 "帶有驗證的結果物件" 模式像是前兩種方法的組合. 即使有一條驗證規則不通過, 但是我們仍然可以鏈接錯誤訊息, 繼續往下執行其他的驗證規則.